/*
        Copyright (C) 2007  Aaron Defazio

        This program is free software; you can redistribute it and/or
        modify it under the terms of the GNU General Public License
        as published by the Free Software Foundation; either version 2
        of the License, or (at your option) any later version.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program; if not, write to the Free Software
        Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
using PluginInterface;

namespace AppSearchPlugin
{
    [Serializable] 
    public class DataStore
    {
        //keyed by full file path
        Dictionary<string, RData> HT;
        //keyed by keyword, values correspond to keys from the HT
        SortedList<string, List<string>> SL;

        public DataStore() {
            HT = new Dictionary<string, RData>();
            SL = new SortedList< string, List<string> >();           
        }
        /// <summary>
        /// Add to Datastore
        /// </summary>
        /// <param name="key">unique key</param>
        /// <param name="value">related infomation class</param>
        public void add(string key, RData value)
        {
            if (HT.ContainsKey(key))
            {
                //Already in datastore
                return;
            }
            //Add to HT
            HT.Add(key, value);                     

            //Add to SL
            foreach (string keyword in value.keywords)
            {
                string Lkeyword = keyword.ToLower();

                if (SL.ContainsKey(Lkeyword))
                {
                    SL[Lkeyword].Add(key);
                }
                else
                {
                    SL.Add(Lkeyword, new List<string>());
                    SL[Lkeyword].Add(key);
                }
            }            
        }

        /// <summary>
        /// Retrieve a dataset using a search term
        /// </summary>
        /// <param name="st">Search Term</param>
        /// <param name="num">Number of results to retrieve</param>
        /// <returns></returns>
        public List<RObject> Retrieve(string st, int num) {
            List<RObject> ro = new List<RObject>(); //list to return
            st = st.ToLower();
            string[] keywords = st.Split(' ');

            if(st == null || st == "" ) return new List<RObject>(); //cover our arse

            foreach (string keyword in keywords)
            {
                //Do SL lookup              
               int Match = NearestMatchBinarySearch( keyword, 0, SL.Count);
               AddToList(Match, ro);

                //Try also adding keywords 'near' the match in the table
               for (int i = 0; ro.Count < num && i <= 10 ; i++)
               {
                   //add i above
                   if(Match+i <= (SL.Count-1) )
                        AddToList(Match + i, ro);
                   //Add i below
                   if(Match-i >= 0)
                    AddToList(Match - i, ro);
                  
               }
            }
            //trim number of results to num
            return ro.GetRange(0, num);
        }

        void AddToList(int num, List<RObject> ro)
        {
            List<string> keys = SL[SL.Keys[num]];    //list of keys for HT, corresponding to RObjects to use

            List<RDataTuple> matches = new List<RDataTuple>();

            foreach (string key in keys)
            {
                RData rd;
                if (!HT.TryGetValue(key, out rd))
                {
                    Debug.Assert(false); //HT MUST contain the value
                }
                matches.Add(new RDataTuple(key,rd));
            }

            matches.Sort();

            foreach( RDataTuple match in matches)
            {
                if (!ro.Exists(delegate(RObject o) { return o.Key == match.key; })) //Anonymous delegate, kinda like a lambda function
                {
                    ro.Add(new RObject(match.key, match.rd.Name, match.rd.icon));
                }
            }

        }

        int NearestMatchBinarySearch(string keyword, int lower, int upper)
        {
            int middle = ((upper - lower) / 2) + lower; //rounds down

            string test = SL.Keys[middle];
            if(test.Length > keyword.Length)
                test = test.Remove(keyword.Length); //trim characters from end, so strings are same length
            int comp = string.Compare(test, keyword);

            if( (upper-lower) == 1) // only two elements
            {   
                //special case
                if (comp == 0)
                    return middle;
                else if ( comp > 0)
                    return lower;
                else
                    return upper;
            }

            //Found nearest match?
            if (upper == lower)
                return lower;

            //Only compare against the matching starting portion of the string in the SL
            if (comp == 0)
                return middle;
            else if (comp > 0)
                return NearestMatchBinarySearch(keyword, lower, middle);
            else
                return NearestMatchBinarySearch(keyword, middle, upper);

        }



        public void IncreasePopularity(string key)
        {
            RData rd = new RData();
            if (!HT.TryGetValue(key, out rd))
            {
                Debug.Assert(false); //HT MUST contain the value
            }
            rd.popularity++;
        }
    }
    

    //we don't store the key in the RData field to save abit of space, but for sorting, we need to keep them together sometimes
    class RDataTuple : IComparable<RDataTuple>
    {
        public string key;
        public RData rd;

        public RDataTuple(string key, RData rd) 
        {
            this.key = key;
            this.rd = rd;
        }

        #region IComparable<RDataTuple> Members

        public int CompareTo(RDataTuple other)
        {
            return rd.CompareTo(other.rd);
        }

        #endregion
    }

    [Serializable] 
public class RData : IComparable<RData>
    {
        public string[] keywords;
        public int popularity;
        public System.Drawing.Icon icon;
        public string Name;

        public RData(string Name, string[] keywords, int popularity, System.Drawing.Icon icon)
        {
            this.Name = Name;
            this.keywords = keywords;
            this.popularity = popularity;
            this.icon = icon;
        }
        public RData()
        {
        }


        #region IComparable<RData> Members
        /// <summary>
        /// We want ordering by popularity, largest to smallest, and tied results should 
        /// be ordered by the length of there name in words.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(RData other)
        {
            int r = this.popularity.CompareTo(other.popularity);
            if (r != 0)
            {   
                //reverse ordering
                r = r * (-1);
            }
            else
            {
                int thislength = this.Name.Split(' ').Length;
                int otherlength = other.Name.Split(' ').Length;
                r = thislength.CompareTo(otherlength);
            }
            return r;
        }

        #endregion
    }


}
